package sf.tools.reflect;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sf.spring.util.CollectionUtils;
import sf.spring.util.StringUtils;
import sf.tools.reflectasm.MethodAccess;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class FastBeanUtils {
    private static final Logger LOGGER = LoggerFactory.getLogger(FastBeanUtils.class);
    public static Map<Class<?>, ClassInfo> methodMap = new ConcurrentHashMap<>();

    private static Lock classLock = new ReentrantLock();

    /**
     * 基于get set方法的快速拷贝
     * @param source 源对象
     * @param target 目标对象
     */
    public static void copyProperties(Object source, Object target) {
        copyProperties(source, target, false, true);
    }

    /**
     * 基于get set方法的快速拷贝
     * 注意此方法拷贝会将所有属性遍历一遍(null的属性可以通过开关设置)
     * 默认只会做同名，同类型属性的copier,否则就会报错.
     * 注意对于集合类型最好单独处理.
     * @param source        源对象
     * @param target        目标对象
     * @param copyNull      是否拷贝null值
     * @param copyMapOrList 是否拷贝集合类型
     */
    public static void copyProperties(Object source, Object target, boolean copyNull, boolean copyMapOrList) {
        ClassInfo targetASM = methodMap.get(target.getClass());
        if (targetASM == null) {
            targetASM = cache(target.getClass());
        }
        ClassInfo sourceASM = methodMap.get(source.getClass());
        if (sourceASM == null) {
            sourceASM = cache(source.getClass());
        }

        Map<String, FieldInfo> fieldMap = targetASM.infoMap;
        for (Map.Entry<String, FieldInfo> entry : fieldMap.entrySet()) {
            FieldInfo targetInfo = entry.getValue();
            Integer targetSetIndex = targetInfo.setterIndex;
            FieldInfo sourceInfo = sourceASM.infoMap.get(entry.getKey());
            if (sourceInfo != null) {
                if (targetInfo.clz != sourceInfo.clz) {//类型不一致,则跳过
                    continue;
                }
                if (sourceInfo.isCollection()) {//判断集合类型和集合的泛型是否一致
                    if (!copyMapOrList) {
                        continue;
                    }
                    Type[] sourceTypes = sourceInfo.actualTypeArguments;
                    Type[] targetTypes = targetInfo.actualTypeArguments;
                    boolean canDo = false;
                    if (targetTypes == null) {
                        canDo = true;
                    } else if (sourceTypes != null) {
                        if (sourceTypes.length == targetTypes.length) {
                            canDo = true;
                            for (int i = 0; i < sourceTypes.length; i++) {
                                if (sourceTypes[i] != targetTypes[i]) {
                                    canDo = false;
                                    break;
                                }
                            }
                        }
                    }
                    if (!canDo) {
                        continue;
                    }
                }
                Integer getIndex = sourceInfo.getterIndex;
                if (targetSetIndex != null && getIndex != null) {
                    Object o = sourceASM.getAccess().invoke(source, getIndex);
                    if (!copyNull && o == null) {
                        continue;
                    }
                    targetASM.getAccess().invoke(target, targetSetIndex, o);
                }
            }
        }
    }

    /**
     * bean转map的快速方法
     * @param bean
     * @return
     */
    public static Map<String, Object> bean2map(Object bean) {
        return bean2map(bean, true);
    }

    /**
     * bean转map的快速方法
     * @param bean
     * @param notNull 是否不为null
     * @return
     */
    public static Map<String, Object> bean2map(Object bean, boolean notNull) {
        if (bean == null) {
            return Collections.emptyMap();
        }
        ClassInfo classInfo = methodMap.get(bean.getClass());
        if (classInfo == null) {
            classInfo = cache(bean.getClass());
        }
        Map<String, Object> map = new HashMap<>();
        Map<String, FieldInfo> fieldMap = classInfo.infoMap;
        for (Map.Entry<String, FieldInfo> entry : fieldMap.entrySet()) {
            FieldInfo fieldInfo = entry.getValue();
            Integer getterIndex = fieldInfo.getterIndex;
            if (getterIndex != null) {
                Object o = classInfo.getAccess().invoke(bean, getterIndex);
                if (notNull && o == null) {
                    continue;
                }
                map.put(fieldInfo.name, o);
            }
        }
        return map;
    }

    public static <T> T map2bean(Map<String, Object> map, Class<T> clz) {
        if (CollectionUtils.isEmpty(map)) {
            return null;
        }
        //创建一个需要转换为的类型的对象
        T obj = null;
        ClassInfo classInfo = methodMap.get(clz);
        if (classInfo == null) {
            classInfo = cache(clz);
        }
        try {
            obj = clz.getDeclaredConstructor().newInstance();
            for (Map.Entry<String, FieldInfo> entry : classInfo.infoMap.entrySet()) {
                FieldInfo fieldInfo = entry.getValue();
                Integer setterIndex = fieldInfo.setterIndex;
                if (setterIndex != null) {
                    Object value = map.get(fieldInfo.name);
                    if (value != null && value.getClass() != fieldInfo.clz) {
                        if (LOGGER.isDebugEnabled()) {
                            LOGGER.debug("bean field=" + fieldInfo.name + ",type=" + fieldInfo.clz
                                    + " not match map value type=" + value.getClass());
                        }
                        continue;
                    }
                    classInfo.getAccess().invoke(obj, setterIndex, value);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return obj;
    }

    // 单例模式
    private static <T> ClassInfo cache(Class<T> clz) {
        classLock.lock();
        try {
            ClassInfo reflectASM = new ClassInfo();
            MethodAccess methodAccess = MethodAccess.get(clz);
            List<Field> fields = new ArrayList<>();
            Collections.addAll(fields, clz.getDeclaredFields());
            List<Method> methods = new ArrayList<>();
            Collections.addAll(methods, clz.getDeclaredMethods());
            Class<?> clazz = clz.getSuperclass();
            for (; clazz != null && clazz != Object.class; clazz = clazz.getSuperclass()) {
                Collections.addAll(fields, clazz.getDeclaredFields());
                Collections.addAll(methods, clazz.getDeclaredMethods());
            }
            Map<String, FieldInfo> fieldMap = new HashMap<>(fields.size());
            for (Field field : fields) {
                if (/*Modifier.isPrivate(field.getModifiers())
                        &&*/ !Modifier.isStatic(field.getModifiers())) { // 是否是私有的，是否是静态的
                    // 非公共私有变量
                    String fieldName = StringUtils.capitalize(field.getName()); // 获取属性名称
                    String getMethodName = null;
                    if (field.getType() == boolean.class) {
                        getMethodName = "is" + fieldName;
                    } else {
                        getMethodName = "get" + fieldName;
                    }
                    String setMethodName = "set" + fieldName;
                    FieldInfo info = new FieldInfo();
                    for (Method m : methods) {
                        if (setMethodName.equals(m.getName()) && m.getParameterCount() == 1) {
                            info.setterIndex = methodAccess.getIndex(setMethodName);
                        } else if (getMethodName.equals(m.getName()) && m.getParameterCount() == 0) {
                            info.getterIndex = methodAccess.getIndex(getMethodName);
                        }
                    }
                    info.name = field.getName();
                    info.clz = field.getType();
                    info.collection = Collection.class.isAssignableFrom(info.clz) || Map.class.isAssignableFrom(info.clz);
                    if (info.collection) {
                        Type type = field.getGenericType();
                        if (type instanceof ParameterizedType) {
                            info.actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
                        }
                    }
                    fieldMap.put(field.getName(), info); // 将属性名称放入集合里
                }
            }
            reflectASM.setAccess(methodAccess);
            reflectASM.setInfoMap(fieldMap);
            methodMap.put(clz, reflectASM);
            return reflectASM;
        } finally {
            classLock.unlock();
        }
    }

    public static class ClassInfo {
        MethodAccess access;
        /**
         * 字段名称<--->字段属性
         */
        Map<String, FieldInfo> infoMap = Collections.emptyMap();

        public MethodAccess getAccess() {
            return access;
        }

        public void setAccess(MethodAccess access) {
            this.access = access;
        }

        public Map<String, FieldInfo> getInfoMap() {
            return infoMap;
        }

        public void setInfoMap(Map<String, FieldInfo> infoMap) {
            this.infoMap = infoMap;
        }
    }


    public static class FieldInfo {
        private Integer setterIndex;
        private Integer getterIndex;
        private String name;
        private Class<?> clz;
        private Type[] actualTypeArguments;
        private boolean collection;//是否是集合

        public Integer getSetterIndex() {
            return setterIndex;
        }

        public void setSetterIndex(Integer setterIndex) {
            this.setterIndex = setterIndex;
        }

        public Integer getGetterIndex() {
            return getterIndex;
        }

        public void setGetterIndex(Integer getterIndex) {
            this.getterIndex = getterIndex;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Class<?> getClz() {
            return clz;
        }

        public void setClz(Class<?> clz) {
            this.clz = clz;
        }

        public Type[] getActualTypeArguments() {
            return actualTypeArguments;
        }

        public void setActualTypeArguments(Type[] actualTypeArguments) {
            this.actualTypeArguments = actualTypeArguments;
        }

        public boolean isCollection() {
            return collection;
        }

        public void setCollection(boolean collection) {
            this.collection = collection;
        }
    }
}
